package com.arpa.wms.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateRange;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.arpa.ntocc.common.common.cache.PartyCache;
import com.arpa.ntocc.common.common.cache.ShipmentCache;
import com.arpa.ntocc.common.common.exception.ServiceException;
import com.arpa.ntocc.common.common.util.CommonUtil;
import com.arpa.ntocc.common.common.util.UserUtil;
import com.arpa.wms.cache.WarehouseCache;
import com.arpa.wms.domain.consts.TipConst;
import com.arpa.wms.domain.dto.ContractDTO;
import com.arpa.wms.domain.dto.ContractSchedulingInventoryDTO;
import com.arpa.wms.domain.entity.*;
import com.arpa.wms.domain.enums.ExpenseDetailStatusEnum;
import com.arpa.wms.domain.enums.StatusEnum;
import com.arpa.wms.domain.enums.StorageFeeTypeEnum;
import com.arpa.wms.domain.vo.ContractOperationCostVO;
import com.arpa.wms.domain.vo.ContractVO;
import com.arpa.wms.mapper.ContractMapper;
import com.arpa.wms.mapper.ExpenseDetailMapper;
import com.arpa.wms.mapper.WarehouseShipmentMapper;
import com.arpa.wms.service.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

/**
 * <p>
 * 仓库计费合同 服务实现类
 * </p>
 *
 * @author LEO
 * @since 2020-11-17
 */
@Slf4j
@Service
public class ContractServiceImpl extends ServiceImpl<ContractMapper, Contract> implements IContractService {

    @Autowired
    private ShipmentCache shipmentCache;
    @Resource
    private PartyCache partyCache;
    @Resource
    private WarehouseCache warehouseCache;
    @Resource
    private WarehouseShipmentMapper warehouseShipmentMapper;
    @Resource
    private ExpenseDetailMapper expenseDetailMapper;

    private final IInventoryService inventoryService;

    private final IContractOperationCostService contractOperationCostService;

    private final IInboundItemService inboundItemService;

    private final IGoodsUnitConversionService goodsUnitConversionService;

    public ContractServiceImpl(IContractOperationCostService contractOperationCostService, IInventoryService inventoryService,
                               IInboundItemService inboundItemService, IGoodsUnitConversionService goodsUnitConversionService) {
        this.contractOperationCostService = contractOperationCostService;
        this.inventoryService = inventoryService;
        this.inboundItemService = inboundItemService;
        this.goodsUnitConversionService = goodsUnitConversionService;
    }

    private static final String BILLING = "billing";
    private static final String SETTLED_BEGIN = "settledBegin";
    /**
     * 保存
     *
     * @param entity
     * @return
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean save(Contract entity) {

        verificationContract(entity);
        entity.setCode(IdUtil.simpleUUID());
        if(entity.getStorageFeeType().equals(StorageFeeTypeEnum.WHOLE)) {
            entity.setAmountMoney(ObjectUtil.defaultIfNull(entity.getUnitPrice(), BigDecimal.ZERO).multiply(ObjectUtil.defaultIfNull(entity.getQuantity(), BigDecimal.ZERO)));
        }
        entity.setStatus(StatusEnum.UNACTIVE.getValue());
        entity.setGroupCode(UserUtil.getBranchCode());
        entity.setCreatedBy(UserUtil.getCode());
        entity.setCreatedName(partyCache.translate(UserUtil.getCode()));
        entity.setModifiedBy(UserUtil.getCode());
        entity.setModifiedName(partyCache.translate(UserUtil.getCode()));
        if (StrUtil.isBlank(entity.getWarehouseName()) && StrUtil.isNotBlank(entity.getWarehouseCode())) {
            entity.setWarehouseName(warehouseCache.translate(entity.getWarehouseCode()));
        }
        if (StrUtil.isBlank(entity.getShipmentName()) && StrUtil.isNotBlank(entity.getShipmentCode())) {
            entity.setShipmentName(shipmentCache.translate(entity.getShipmentCode()));
        }
        //新增作业费
        createContractOperationCost(entity);
        return super.save(entity);
    }

    /**
     * 根据code更新实体
     *
     * @param entity
     * @return
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean updateByCode(Contract entity) {


        Contract oldEntity = super.getOne(new QueryWrapper<Contract>().lambda().eq(Contract::getCode, entity.getCode()).eq(Contract::getGroupCode, UserUtil.getBranchCode()));
        if (null == oldEntity) {
            log.error("修改合同【{}】失败，找不到合同信息", entity.getCode());
            throw new ServiceException("修改合同【" + entity.getCode() + "】失败，找不到合同信息");
        }
        verificationContract(entity);
        entity.setId(oldEntity.getId());
        BeanUtil.copyProperties(entity, oldEntity, CopyOptions.create().setIgnoreProperties("code"));

        if (StorageFeeTypeEnum.WHOLE.equals(entity.getStorageFeeType())) {
            entity.setAmountMoney(ObjectUtil.defaultIfNull(entity.getUnitPrice(), BigDecimal.ZERO).multiply(ObjectUtil.defaultIfNull(entity.getQuantity(), BigDecimal.ZERO)));
        }
        entity.setModifiedBy(UserUtil.getCode());
        entity.setModifiedName(partyCache.translate(UserUtil.getCode()));
        if (StrUtil.isBlank(entity.getWarehouseName()) && StrUtil.isNotBlank(entity.getWarehouseCode())) {
            entity.setWarehouseName(warehouseCache.translate(entity.getWarehouseCode()));
        }
        if (StrUtil.isBlank(entity.getShipmentName()) && StrUtil.isNotBlank(entity.getShipmentCode())) {
            entity.setShipmentName(shipmentCache.translate(entity.getShipmentCode()));
        }
        //清除所有作业费
        contractOperationCostService.remove(new QueryWrapper<ContractOperationCost>().lambda().eq(ContractOperationCost::getContractCode, entity.getCode()).eq(ContractOperationCost::getGroupCode,UserUtil.getBranchCode()));
        //新增作业费
        createContractOperationCost(entity);
        boolean bool = super.updateById(oldEntity);

        return bool;
    }


    /**
     * 查询列表
     *
     * @param contractDTO
     * @return
     */
    @Override
    public List<ContractVO> queryList(ContractDTO contractDTO) {
        //排序字段名需要驼峰转数据库下划线类型字段名
        if (StringUtils.isNotEmpty(contractDTO.getSortField())) {
            contractDTO.setSortField(CommonUtil.camel2Underline(contractDTO.getSortField()));
        }
        List<ContractVO> contractVOList = baseMapper.queryList(contractDTO);
        return contractVOList;
    }

    /**
     * 查询合计,包含总数
     *
     * @param contractDTO
     * @return
     */
    @Override
    public ContractVO queryListSum(ContractDTO contractDTO) {
        ContractVO contractVO = baseMapper.queryListSum(contractDTO);
        return contractVO;
    }

    /**
     * 计费合同启用
     *
     * @param codes 合同标识
     * @return
     */
    @Override
    public int activeStatus(final List<String> codes) {
        int count = baseMapper.changeStatusByContractCodes(codes, StatusEnum.ACTIVE.getValue(),UserUtil.getBranchCode());
        return count;
    }


    /**
     * 启用合同
     *
     * @param code 合同标识
     */
    @Override
    @Async
    @Transactional(rollbackFor = Exception.class)
    public Future<Boolean> active(final String code,String groupCode) {
        log.info("启用合同启动线程" + Thread.currentThread().getName() + " 执行异步启用合同任务：" + code);
        Contract entity = super.getOne(new QueryWrapper<Contract>().lambda().eq(Contract::getCode, code).eq(Contract::getGroupCode, groupCode));
        if (null == entity) {
            log.error("启用合同【{}】失败，找不到合同信息", code);
            throw new ServiceException("启用合同【" + code + "】失败，找不到合同信息");
        }

        if (StrUtil.equals(entity.getStatus(), StatusEnum.ACTIVE.getValue())) {
            log.error("启用合同【{}】失败，合同已启用，不能重复启用", code);
            throw new ServiceException("启用合同【" + entity.getContractNo() + "】失败，不能重复启用");
        }

        boolean updateStatus = super.update(new UpdateWrapper<Contract>().lambda().eq(Contract::getCode, code).eq(Contract::getGroupCode,groupCode)
                .set(Contract::getStatus, StatusEnum.ACTIVE)
        );
        log.info("启用合同【{}】，启用结果【{}】", code, updateStatus);
        return new AsyncResult(true);
    }


    /**
     * 停用合同
     *
     * @param code 合同标识
     */
    @Override
    @Async
    @Transactional(rollbackFor = Exception.class)
    public Future<Boolean> unactive(final String code,String groupCode) {
        log.info("停用合同启动线程" + Thread.currentThread().getName() + " 执行异步停用合同任务：" + code);
        Contract entity = super.getOne(new QueryWrapper<Contract>().lambda().eq(Contract::getCode, code).eq(Contract::getGroupCode,groupCode));
        if (entity == null) {
            log.error("停用合同失败，找不到合同【{}】", code);
            throw new ServiceException("停用合同失败，找不到合同【" + code + "】");
        }
        boolean updateStatus = super.update(new UpdateWrapper<Contract>().lambda().eq(Contract::getCode, code).eq(Contract::getGroupCode,groupCode)
                .set(Contract::getStatus, 1)
        );
        log.info("停用合同【{}】，停用结果【{}】", code, updateStatus);
        return new AsyncResult(true);
    }

    /**
     * 包仓生成仓储费用排期
     *
     * 已废弃，计费逻辑优化后不需要排期
     *
     * @param entity 合同信息
     */
    @Deprecated
    private void wholeGenerateBillingSchedule(final Contract entity) {
        StringBuilder sql = new StringBuilder();
        sql.append("INSERT INTO `wms_contract_scheduling`(`id`, `code`, `contract_code`, `gmt_billing`, `gmt_settled_begin`, `status`, `gmt_created`, `gmt_modified`,`storage_fee_type`, `billing_unit`, `unit_price`, `quantity`, `min_billing_volume`,`warehouse_code`,`shipment_code`,`shipment_name`,`group_code`) VALUES ");
        //计费周期
        int billingCycle = entity.getBillingCycle();
        //开始计费日期
        LocalDate gmtBillingBegin = entity.getGmtBillingBegin();
        //失效日期
        LocalDate gmtIneffective = entity.getGmtIneffective();
        Date billingBegin = java.sql.Date.valueOf(gmtBillingBegin);
        Date ineffective = java.sql.Date.valueOf(gmtIneffective);
        //计费排期
        List<Map<String, String>> date = generateRange(billingBegin, ineffective, billingCycle);
        List<StringBuilder> builders = new ArrayList<>();
        date.forEach(d -> {
            StringBuilder sqlValueStr = new StringBuilder();
            sqlValueStr.append("(");
            sqlValueStr.append("default,");
            sqlValueStr.append("'").append(IdUtil.simpleUUID()).append("',");
            //合同标识
            sqlValueStr.append("'").append(entity.getCode()).append("',");
            //计费日期
            sqlValueStr.append("'").append(MapUtil.getStr(d, BILLING)).append("',");
            //计费开始日期
            sqlValueStr.append("'").append(MapUtil.getStr(d, SETTLED_BEGIN)).append("',");
            //状态:0未计费，1已计费
            sqlValueStr.append("'0',");
            //创建日期
            sqlValueStr.append("'").append(LocalDateTime.now()).append("',");
            //修改日期
            sqlValueStr.append("'").append(LocalDateTime.now()).append("',");
            //合同类型
            sqlValueStr.append("'").append(entity.getStorageFeeType().getValue()).append("',");
            //计费单位：包仓（LOCATION（库位），AREA（面积）），零仓（PIECE（件），TON（吨），LOCATION（库位））
            sqlValueStr.append("'").append(entity.getBillingUnit().getValue()).append("',");
            //单价
            sqlValueStr.append(entity.getUnitPrice()).append(",");
            //数量
            sqlValueStr.append(entity.getQuantity()).append(",");
            //最小计费量
            sqlValueStr.append(entity.getMinBillingVolume()).append(",");
            //仓库
            sqlValueStr.append("'").append(entity.getWarehouseCode()).append("',");
            //货主标识
            sqlValueStr.append("'").append(entity.getShipmentCode()).append("',");
            //货主名称
            sqlValueStr.append("'").append(entity.getShipmentName()).append("',");
            sqlValueStr.append("'").append(UserUtil.getBranchCode()).append("'");

            sqlValueStr.append(")");
            builders.add(sqlValueStr);
        });
        if (builders.size() == 0) {
            log.error("合同启动失败，创建计费排期失败");
            throw new ServiceException("合同启动失败，创建计费排期失败");
        }
        String valueStr = builders.stream().map(StringBuilder::toString).collect(Collectors.joining(","));
        sql.append(valueStr).append(";");
        baseMapper.generateBillingSchedule(sql.toString());
    }


    /**
     * 零仓合同初始化
     * 合同编辑、启用时使用
     * 已废弃
     * @param entity 零仓合同
     */
    @Deprecated
    private void scatteredInitializationBillingSchedule(Contract entity) {
        if (entity != null) {
            //通过商品code，收货批次分组获取货主所有正式库存
            List<Map<String, Object>> inventories = inventoryService.listMaps(
                    new LambdaQueryWrapper<Inventory>().select(Inventory::getWarehouseCode,Inventory::getGoodsCode,
                    Inventory::getGoodCode,Inventory::getGoodsName,Inventory::getReceiveLot)
                    .eq(Inventory::getShipmentCode, entity.getShipmentCode())
                    .gt(Inventory::getQuantity, BigDecimal.ZERO)
                    .eq(Inventory::getTempInventory, 0)
                    .eq(Inventory::getWarehouseCode, entity.getWarehouseCode())
                            .eq(Inventory::getGroupCode,UserUtil.getBranchCode())
                    .isNotNull(Inventory::getReceiveLot)
                    .groupBy(Inventory::getWarehouseCode,Inventory::getGoodsCode, Inventory::getReceiveLot)
            );
            if (IterUtil.isEmpty(inventories)) {
                //货主当前没有上架库存
                return;
            }
            StringBuilder sql = new StringBuilder();
            sql.append("INSERT INTO `wms_contract_scheduling`(`id`, `code`, `contract_code`, `gmt_billing`, `gmt_settled_begin`," +
                    "`status`, `gmt_created`, `gmt_modified`,`storage_fee_type`, `inbound_code`, `gmt_inbound_created`,`billing_unit`, `unit_price`," +
                    " `min_billing_volume`,`warehouse_code`,`shipment_code`,`shipment_name`,`goods_code`,`receive_lot`," +
                    "`group_code`,`good_code`,`goods_name`) VALUES ");
            //计费周期
            int billingCycle = entity.getBillingCycle();
            List<StringBuilder> builders = new ArrayList<>();
            inventories.forEach(r -> {

                //通过库存获取入库单号及入库时间
                Map<String, Object> item = inboundItemService.getItemInfo(MapUtil.getStr(r, "warehouse_code"),MapUtil.getStr(r, "goods_code"),MapUtil.getStr(r, "receive_lot"));
                if(MapUtil.isNotEmpty(item)) {


                    //零仓合同中途修改后，开始计费日期为当前日期
                    //用入库日期
                    Date gmtCreated = MapUtil.getDate(item,"gmtCreated");
                    //失效日期
                    LocalDate gmtIneffective = entity.getGmtIneffective();
                    Date billingBegin = DateUtil.date(gmtCreated);
                    Date ineffective = java.sql.Date.valueOf(gmtIneffective);
                    //计费排期
                    List<Map<String, String>> date = generateRange(billingBegin, ineffective, billingCycle);

                    date.forEach(d -> {
                        StringBuilder sqlValueStr = new StringBuilder();
                        sqlValueStr.append("(");
                        //id
                        sqlValueStr.append("default,");
                        //code
                        sqlValueStr.append("'").append(IdUtil.simpleUUID()).append("',");
                        //合同标识:contract_code
                        sqlValueStr.append("'").append(entity.getCode()).append("',");
                        //计费日期:gmt_billing
                        sqlValueStr.append("'").append(MapUtil.getStr(d, BILLING)).append("',");
                        //计费开始日期:gmt_settled_begin
                        sqlValueStr.append("'").append(MapUtil.getStr(d, SETTLED_BEGIN)).append("',");
                        //状态:0未计费，1已计费:status
                        sqlValueStr.append("'0',");
                        //创建日期:gmt_created
                        sqlValueStr.append("'").append(LocalDateTime.now()).append("',");
                        //修改日期:gmt_modified
                        sqlValueStr.append("'").append(LocalDateTime.now()).append("',");
                        //合同类型:storage_fee_type
                        sqlValueStr.append("'").append(entity.getStorageFeeType().getValue()).append("',");
                        //inbound_code
                        sqlValueStr.append("'").append(MapUtil.getStr(item, "inboundCode")).append("',");
                        //gmt_inbound_created
                        sqlValueStr.append("'").append(billingBegin).append("',");
                        //计费单位：包仓（LOCATION（库位），AREA（面积）），零仓（PIECE（件），TON（吨），LOCATION（库位））
                        sqlValueStr.append("'").append(entity.getBillingUnit().getValue()).append("',");
                        //单价:unit_price
                        sqlValueStr.append(entity.getUnitPrice()).append(",");
                        //最小计费量:min_billing_volume
                        sqlValueStr.append(entity.getMinBillingVolume()).append(",");
                        //仓库:warehouse_code
                        sqlValueStr.append("'").append(entity.getWarehouseCode()).append("',");
                        //货主标识:shipment_code
                        sqlValueStr.append("'").append(entity.getShipmentCode()).append("',");
                        //货主名称:shipment_name
                        sqlValueStr.append("'").append(entity.getShipmentName()).append("'").append(",");
                        //商品标识:goods_code
                        sqlValueStr.append("'").append(MapUtil.getStr(r, "goods_code")).append("'").append(",");
                        //收货批次
                        sqlValueStr.append("'").append(MapUtil.getStr(r, "receive_lot")).append("',");
                        sqlValueStr.append("'").append(UserUtil.getBranchCode()).append("',");
                        //商品编号
                        sqlValueStr.append("'").append(MapUtil.getStr(r, "good_code")).append("',");
                        //商品名称
                        sqlValueStr.append("'").append(MapUtil.getStr(r, "goods_name")).append("'");
                        sqlValueStr.append(")");
                        builders.add(sqlValueStr);
                    });
                }
            });

            if(IterUtil.isNotEmpty(builders)) {
                String valueStr = builders.stream().map(StringBuilder::toString).collect(Collectors.joining(","));
                sql.append(valueStr).append(";");


                baseMapper.generateBillingSchedule(sql.toString());
            }
        }


    }


    /**
     * 商品入库，零仓合同生成计费排期
     *  新入库先生成一条费用明细
     * TODO 入库商品调用此方法生成零仓合同排期
     *
     * @param contractSchedulingInventoryDTO 入库商品
     */
    @Override
    @Async
    @Transactional(rollbackFor = Exception.class)
    public void scatteredGenerateBillingSchedule(final ContractSchedulingInventoryDTO contractSchedulingInventoryDTO) {
        String groupCode = contractSchedulingInventoryDTO.getGroupCode();

        if (StrUtil.isBlank(contractSchedulingInventoryDTO.getReceiveLot())) {
            //无收货批次
            return;
        }
        BigDecimal quantity = contractSchedulingInventoryDTO.getQuantity();

        //计费数量
        if (quantity.compareTo(BigDecimal.ZERO) == 0) {
            //库存为0不及费用
            return;
        }

        Contract entity = super.getOne(new QueryWrapper<Contract>().lambda()
                .eq(Contract::getGroupCode,groupCode)
                .eq(Contract::getStatus, "0")
                .eq(Contract::getWarehouseCode,contractSchedulingInventoryDTO.getWarehouseCode())
                .eq(Contract::getShipmentCode, contractSchedulingInventoryDTO.getShipmentCode())
                .eq(Contract::getStorageFeeType, StorageFeeTypeEnum.SCATTERED)
                .le(Contract::getGmtEffective, contractSchedulingInventoryDTO.getGmtInboundCreated())
                .ge(Contract::getGmtIneffective, contractSchedulingInventoryDTO.getGmtInboundCreated())
                .gt(Contract::getUnitPrice, BigDecimal.ZERO)
                .isNotNull(Contract::getBillingUnit)
        );
        log.info("查找货主【{}】的零仓计费合同【{}】", contractSchedulingInventoryDTO.getShipmentName(), entity);
        if (null == entity) {
            log.error("找不到货主【{}】符合条件的仓储合同",contractSchedulingInventoryDTO.getShipmentCode());
            return;
        }

      /*  //计费周期
        int billingCycle = entity.getBillingCycle();
        //开始计费日期
        LocalDate gmtBillingBegin = contractSchedulingInventoryDTO.getGmtInboundCreated();
        //失效日期
        LocalDate gmtIneffective = entity.getGmtIneffective();
        Date billingBegin = java.sql.Date.valueOf(gmtBillingBegin);
        Date ineffective = java.sql.Date.valueOf(gmtIneffective);
        //计费排期
        List<Map<String, String>> date = generateRange(billingBegin, ineffective, billingCycle);


        StringBuilder sql = new StringBuilder();
        sql.append("INSERT INTO `wms_contract_scheduling`(`id`, `code`, `contract_code`, `gmt_billing`, `gmt_settled_begin`," +
                "`status`, `gmt_created`, `gmt_modified`,`storage_fee_type`, `inbound_code`, `gmt_inbound_created`,`billing_unit`, `unit_price`, " +
                "`min_billing_volume`,`warehouse_code`,`shipment_code`,`shipment_name`," +
                "`goods_code`,`receive_lot`,`group_code`,`good_code`,`goods_name`) VALUES ");


        List<StringBuilder> builders = new ArrayList<>();
        date.forEach(d -> {
            StringBuilder sqlValueStr = new StringBuilder();
            sqlValueStr.append("(");
            //id
            sqlValueStr.append("default,");
            //code
            sqlValueStr.append("'").append(IdUtil.simpleUUID()).append("',");
            //合同标识:contract_code
            sqlValueStr.append("'").append(entity.getCode()).append("',");
            //计费日期:gmt_billing
            sqlValueStr.append("'").append(MapUtil.getStr(d, BILLING)).append("',");
            //计费开始日期:gmt_settled_begin
            sqlValueStr.append("'").append(MapUtil.getStr(d, SETTLED_BEGIN)).append("',");
            //状态:0未计费，1已计费:status
            sqlValueStr.append("'0',");
            //创建日期:gmt_created
            sqlValueStr.append("'").append(LocalDateTime.now()).append("',");
            //修改日期:gmt_modified
            sqlValueStr.append("'").append(LocalDateTime.now()).append("',");
            //合同类型:storage_fee_type
            sqlValueStr.append("'").append(entity.getStorageFeeType().getValue()).append("',");
            //inbound_code
            sqlValueStr.append("'").append(contractSchedulingInventoryDTO.getInboundCode()).append("',");
            //gmt_inbound_created
            sqlValueStr.append("'").append(contractSchedulingInventoryDTO.getGmtInboundCreated()).append("',");
            //计费单位：包仓（LOCATION（库位），AREA（面积）），零仓（PIECE（件），TON（吨），LOCATION（库位））
            sqlValueStr.append("'").append(entity.getBillingUnit().getValue()).append("',");
            //单价:unit_price
            sqlValueStr.append(entity.getUnitPrice()).append(",");
            //最小计费量:min_billing_volume
            sqlValueStr.append(entity.getMinBillingVolume()).append(",");
            //仓库:warehouse_code
            sqlValueStr.append("'").append(entity.getWarehouseCode()).append("',");
            //货主标识:shipment_code
            sqlValueStr.append("'").append(entity.getShipmentCode()).append("',");
            //货主名称:shipment_name
            sqlValueStr.append("'").append(entity.getShipmentName()).append("'").append(",");
            //商品标识:goods_code
            sqlValueStr.append("'").append(contractSchedulingInventoryDTO.getGoodsCode()).append("',");
            //收货批次
            sqlValueStr.append("'").append(contractSchedulingInventoryDTO.getReceiveLot()).append("',");
            //机构
            sqlValueStr.append("'").append(UserUtil.getBranchCode()).append("',");
            //商品code
            sqlValueStr.append("'").append(contractSchedulingInventoryDTO.getGoodCode()).append("',");
            //商品名称
            sqlValueStr.append("'").append(contractSchedulingInventoryDTO.getGoodsName()).append("'");
            sqlValueStr.append(")");
            builders.add(sqlValueStr);
        });

        String valueStr = builders.stream().map(StringBuilder::toString).collect(Collectors.joining(","));
        sql.append(valueStr).append(";");
        baseMapper.generateBillingSchedule(sql.toString());*/
        //生成第一笔费用

        BigDecimal weight = BigDecimal.ZERO;
        BigDecimal location ;
        BigDecimal amountMoney;
        //查询重量
        GoodsUnitConversion goodsUnitConversion = goodsUnitConversionService.getOne(new QueryWrapper<GoodsUnitConversion>()
                .lambda().select(GoodsUnitConversion::getWeight)
                .eq(GoodsUnitConversion::getGroupCode,groupCode)
                .eq(GoodsUnitConversion::getGoodsCode, contractSchedulingInventoryDTO.getGoodsCode())
                .eq(GoodsUnitConversion::getFlag, 1));
        if (null != goodsUnitConversion && goodsUnitConversion.getWeight().compareTo(BigDecimal.ZERO) > 0) {
            weight = quantity.multiply(goodsUnitConversion.getWeight()).divide(BigDecimal.valueOf(1000));
        }


        switch (entity.getBillingUnit()) {
            //数量小于最小计费量以最小计费量为准
            case PIECE: {
                //按件收费
                amountMoney = entity.getUnitPrice().multiply(quantity.compareTo(entity.getMinBillingVolume()) < 0 ? entity.getMinBillingVolume() : quantity);
                break;
            }
            case TON: {

                //计费数量
                if (weight.compareTo(BigDecimal.ZERO) == 0) {
                    //库存为0不及费用
                    return;
                }
                //千克转换成吨
                amountMoney = entity.getUnitPrice().multiply(weight.compareTo(entity.getMinBillingVolume()) < 0 ? entity.getMinBillingVolume() : weight);
                break;
            }
            default: {
                //按所占库位
                location = expenseDetailMapper.getBillingQuantityForLocation(entity.getWarehouseCode(), entity.getShipmentCode(), contractSchedulingInventoryDTO.getGoodsCode(), contractSchedulingInventoryDTO.getReceiveLot());
                amountMoney = entity.getUnitPrice().multiply(location.compareTo(entity.getMinBillingVolume()) < 0 ? entity.getMinBillingVolume() : location);
                break;
            }
        }
        if (amountMoney.compareTo(BigDecimal.ZERO) == 0) {
            //库存为0不及费用
            return;
        }
        ExpenseDetail expenseDetail = new ExpenseDetail();
        expenseDetail.setCode(IdUtil.simpleUUID());
        expenseDetail.setGroupCode(entity.getGroupCode());
        expenseDetail.setContractCode(entity.getCode());
        expenseDetail.setWarehouseCode(entity.getWarehouseCode());
        expenseDetail.setShipmentCode(entity.getShipmentCode());
        expenseDetail.setShipmentName(entity.getShipmentName());
        expenseDetail.setGmtOrder(contractSchedulingInventoryDTO.getGmtInboundCreated());
        expenseDetail.setSourceCode(contractSchedulingInventoryDTO.getInboundCode());
        expenseDetail.setGoodsCode(contractSchedulingInventoryDTO.getGoodsCode());
        expenseDetail.setGoodsName(contractSchedulingInventoryDTO.getGoodsName());
        expenseDetail.setGoodCode(contractSchedulingInventoryDTO.getGoodCode());
        expenseDetail.setQuantity(quantity);
        expenseDetail.setWeight(weight);
        expenseDetail.setFeeType("仓储费");
        expenseDetail.setReceiveLot(contractSchedulingInventoryDTO.getReceiveLot());
        expenseDetail.setBillingUnit(entity.getBillingUnit());
        expenseDetail.setUnitPrice(entity.getUnitPrice());
        //计算费用
        expenseDetail.setAmountMoney(amountMoney);
        expenseDetail.setGmtSettledBegin(contractSchedulingInventoryDTO.getGmtInboundCreated());
        expenseDetail.setGmtSettledEnd(contractSchedulingInventoryDTO.getGmtInboundCreated());
        expenseDetail.setStatus(ExpenseDetailStatusEnum.UNSETTLED);
        expenseDetail.setUnsettledMoney(expenseDetail.getAmountMoney());
        expenseDetailMapper.insert(expenseDetail);

    }

    /**
     * 计费合同停用
     *
     * @param codes 合同标识
     * @return
     */
    @Override
    public int unactiveStatus(final List<String> codes) {
        int count = baseMapper.changeStatusByContractCodes(codes, StatusEnum.UNACTIVE.getValue(),UserUtil.getBranchCode());
        return count;
    }

    /**
     * 获取合同详情
     *
     * @param code 合同标识
     * @return
     */
    @Override
    public ContractVO getContract(final String code) {
        Contract entity = super.getOne(new QueryWrapper<Contract>().lambda().eq(Contract::getCode, code).eq(Contract::getGroupCode, UserUtil.getBranchCode()));
        if (null == entity) {
            log.error("查询合同【{}】详情失败，找不到合同信息", code);
            throw new ServiceException("查询合同【" + code + "】详情失败，找不到合同信息");
        }
        ContractVO contractVO = new ContractVO();
        BeanUtil.copyProperties(entity, contractVO, false);
        if(null!=entity.getBillingUnit()) {
            contractVO.setBillingUnit(entity.getBillingUnit().getValue());
        }
        contractVO.setStorageFeeType(entity.getStorageFeeType().getValue());
        if (StrUtil.isBlank(contractVO.getWarehouseName()) && StrUtil.isNotBlank(contractVO.getWarehouseCode())) {
            contractVO.setWarehouseName(warehouseCache.translate(contractVO.getWarehouseCode()));
        }
        if (StrUtil.isBlank(contractVO.getShipmentName()) && StrUtil.isNotBlank(contractVO.getShipmentCode())) {
            contractVO.setShipmentName(shipmentCache.translate(contractVO.getShipmentCode()));
        }

        List<ContractOperationCost> contractOperationCosts = contractOperationCostService.list(new QueryWrapper<ContractOperationCost>().lambda().eq(ContractOperationCost::getContractCode, code).eq(ContractOperationCost::getGroupCode,UserUtil.getBranchCode()));

        List<ContractOperationCostVO> contractOperationCostVos = contractOperationCosts.stream().map(r-> {
                    ContractOperationCostVO temp = BeanUtil.copyProperties(r, ContractOperationCostVO.class);
                    if(null!=r.getBillingUnit()){
                        temp.setBillingUnit(r.getBillingUnit().getValue());
                    }
                    return temp;
                }
                ).collect(Collectors.toList());
        contractVO.setContractOperationCostList(contractOperationCostVos);
        return contractVO;
    }


    /**
     * 通过code删除合同信息
     * 同时删除合同作业费
     * 同时删除合同计费排期
     *
     * @param code 合同标识
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void remove(final String code) {
        int count = super.count(new QueryWrapper<Contract>().lambda().eq(Contract::getCode, code).eq(Contract::getGroupCode, UserUtil.getBranchCode()));
        if (count == 0) {
            log.error("删除合同【{}】失败，找不到合同信息", code);
            throw new ServiceException("删除合同【" + code + "】详情失败，找不到合同信息");
        }
        //只能删除当前机构的信息
        super.remove(new QueryWrapper<Contract>().lambda().eq(Contract::getCode, code).eq(Contract::getGroupCode, UserUtil.getBranchCode()));
        //删除作业费
        contractOperationCostService.remove(new QueryWrapper<ContractOperationCost>().lambda().eq(ContractOperationCost::getContractCode, code).eq(ContractOperationCost::getGroupCode,UserUtil.getBranchCode()));
        //删除计费排期
        //baseMapper.removeBillingSchedule(code);
    }

    @Override
    public List<ContractOperationCost> getContractOperationCosts(final String contractCode) {
        return contractOperationCostService.list(new QueryWrapper<ContractOperationCost>().lambda().eq(ContractOperationCost::getContractCode, contractCode).eq(ContractOperationCost::getGroupCode,UserUtil.getBranchCode()));
    }


    /**
     * 校验合同信息有效性
     * 1、同一货主同一时段不能有两个合同
     * 2、仓库必须为当前机构下已启用并启用库位管理
     * 3、货主必须绑定所选仓库
     *
     * @param entity 合同
     */
    private void verificationContract(Contract entity) {


        if (StrUtil.isBlank(entity.getCode())) {
            int count = baseMapper.checkExistsCount(entity.getWarehouseCode(),entity.getShipmentCode(), entity.getGmtEffective(), entity.getGmtIneffective());
            if (count > 0) {
                log.error("有效期内【{}-{}】已有合同存在", entity.getGmtEffective(), entity.getGmtIneffective());
                throw new ServiceException("有效期内已有合同存在");
            }
        }
        Map<String, Object> warehouseMap = warehouseCache.get(entity.getWarehouseCode());
        if (MapUtil.isEmpty(warehouseMap)) {
            log.error("所选仓库仓库【{}】不存在", entity.getWarehouseCode());
            throw new ServiceException("所选仓库不存在");
        }
        Warehouse warehouse = BeanUtil.mapToBean(warehouseMap, Warehouse.class, false);
        if (!StrUtil.equals(UserUtil.getBranchCode(), warehouse.getGroupCode())) {
            log.error("所选仓库仓库【{}】不属于当前登录用【{}】所属户机构", entity.getWarehouseCode(), UserUtil.getCode());
            throw new ServiceException(TipConst.THE_SELECTED_REPOSITORY_IS_NOT_AVAILABLE);
        }
        if (StrUtil.equals(StatusEnum.UNACTIVE.getValue(), warehouse.getStatus())) {
            log.error("所选仓库仓库【{}】未启用", entity.getWarehouseCode());
            throw new ServiceException(TipConst.THE_SELECTED_REPOSITORY_IS_NOT_AVAILABLE);
        }
        if (warehouse.getLocationManagement() == 0) {
            log.error("所选仓库仓库【{}】未启用库位管理", entity.getWarehouseCode());
            throw new ServiceException(TipConst.THE_SELECTED_REPOSITORY_IS_NOT_AVAILABLE);
        }
        int warehouseShipmentCount = warehouseShipmentMapper.selectCount(new QueryWrapper<WarehouseShipment>().lambda().eq(WarehouseShipment::getWarehouseCode, entity.getWarehouseCode()).eq(WarehouseShipment::getShipmentCode, entity.getShipmentCode()).eq(WarehouseShipment::getGroupCode,UserUtil.getBranchCode()));
        if (warehouseShipmentCount < 1) {
            log.error("仓库【{}】未绑定货主【{}】", entity.getWarehouseCode(), entity.getShipmentCode());
            throw new ServiceException("所选货主不可用");
        }

        if(StorageFeeTypeEnum.WHOLE.equals(entity.getStorageFeeType())) {
            if(ObjectUtil.isNull(entity.getGmtBillingBegin())){
                log.error("开始计费日期不能为空");
                throw new ServiceException("开始计费日期不能为空");
            }
            Date billingBegin = java.sql.Date.valueOf(entity.getGmtBillingBegin());
            long between = DateUtil.between(DateUtil.beginOfDay(billingBegin), DateUtil.beginOfDay(DateUtil.date()), DateUnit.DAY, false);
            if (between > 0) {
                log.error("开始计费日期【{}】早于当前日期【{}】", billingBegin, DateUtil.today());
                throw new ServiceException("开始计费日期必须晚于当前日期");
            }
            Date effective = java.sql.Date.valueOf(entity.getGmtEffective());
            Date ineffective = java.sql.Date.valueOf(entity.getGmtIneffective());
            boolean isIn = DateUtil.isIn(billingBegin, effective, ineffective);
            if (!isIn) {
                log.error("开始计费日期【{}】不在合同有效期内【{}-{}】", entity.getGmtBillingBegin(), entity.getGmtEffective(), entity.getGmtIneffective());
                throw new ServiceException("开始计费日期不在合同有效期内");
            }
        }


    }


    /**
     * 生成合同计费排期
     *  已废弃，新的计费逻辑不需要排期
     * @param entity
     */
    @Deprecated
    private void generateBillingSchedule(final Contract entity) {
        //单价、数量大于0，计费单位不为空才生成计费排期
        if (entity.getUnitPrice().compareTo(BigDecimal.ZERO) > 0 && ObjectUtil.isNotNull(entity.getBillingUnit())) {
            log.info("包仓合同【{}】，创建计费排期", entity.getCode());
            if (entity.getStorageFeeType().equals(StorageFeeTypeEnum.WHOLE) && entity.getQuantity().compareTo(BigDecimal.ZERO) > 0) {
                wholeGenerateBillingSchedule(entity);
            }
            if (entity.getStorageFeeType().equals(StorageFeeTypeEnum.SCATTERED)) {
                scatteredInitializationBillingSchedule(entity);
            }
        }
    }


    /**
     * 构建并新增合同作业费
     *
     * @param entity 合同
     */
    private void createContractOperationCost(final Contract entity) {
        List<ContractOperationCost> contractOperationCosts = entity.getContractOperationCostList();
        if (IterUtil.isNotEmpty(contractOperationCosts)) {
            entity.getContractOperationCostList().forEach(r -> {
                r.setCode(IdUtil.simpleUUID());
                r.setContractCode(entity.getCode());
                r.setCreatedBy(UserUtil.getCode());
                r.setCreatedName(partyCache.translate(UserUtil.getCode()));
                r.setModifiedBy(UserUtil.getCode());
                r.setModifiedName(partyCache.translate(UserUtil.getCode()));
                r.setGroupCode(UserUtil.getBranchCode());
            });
            //保存作业费
            contractOperationCostService.saveBatch(entity.getContractOperationCostList());
        }
    }


    /**
     * 根据开始计费时间、合同失效时间、计费周期获得合同内的计费时间点
     *
     * @param billingBegin 开始计费时间
     * @param ineffective  合同失效时间
     * @param billingCycle 计费周期
     */
    public static List<Map<String, String>> generateRange(final Date billingBegin, final Date ineffective, final int billingCycle) {
        List<Map<String, String>> dateMap = new ArrayList<>();
        //计算开始计费时间与合同有效期之间相差的时长
        long between = DateUtil.between(DateUtil.beginOfDay(billingBegin), DateUtil.beginOfDay(ineffective), DateUnit.DAY, true);
        //判断是否大于一个计费周期
        if (between > billingCycle) {
            DateRange dd = new DateRange(billingBegin, ineffective, DateField.DAY_OF_MONTH, billingCycle, false, true);
            dd.forEach(d -> {
                Map<String, String> map = new HashMap<>();
                //计费周期开始时间
                map.put(SETTLED_BEGIN, d.offsetNew(DateField.DAY_OF_MONTH, -(billingCycle)).toDateStr());
                map.put(BILLING, d.toDateStr());
                dateMap.add(map);
            });
            if (IterUtil.isNotEmpty(dateMap)) {
                int mapSize = dateMap.size();
                Map<String, String> lastMap = dateMap.get(mapSize - 1);
                if (MapUtil.isNotEmpty(lastMap)) {
                    String billing = MapUtil.getStr(lastMap, BILLING);
                    //合同失效前最后一次计费时间是否与合同失效日期相等，不相等追加一次合同失效日期为计费时间
                    if (!StrUtil.equals(billing, DateUtil.formatDate(ineffective))) {
                        Map<String, String> map = new HashMap<>();
                        //计费周期开始时间
                        map.put(SETTLED_BEGIN, billing);
                        map.put(BILLING, DateUtil.formatDate(ineffective));
                        dateMap.add(map);
                    }
                }
            }
        } else {
            Map<String, String> map = new HashMap<>();
            //计费周期开始时间
            map.put(SETTLED_BEGIN, DateUtil.formatDate(billingBegin));
            map.put(BILLING, DateUtil.formatDate(ineffective));
            dateMap.add(map);
        }
        return dateMap;
    }


    /**
     * 获取机构下有效合同
     *
     * @param branchCode 机构code
     * @return
     */
    @Override
    public List<Contract> queryEffective(String branchCode) {
        return baseMapper.queryEffective(branchCode);
    }
}
